package com.wider.foundation.channel;

import com.wider.foundation.DLogger;
import com.wider.foundation.TimeLimited;
import com.wider.foundation.concurrency.QueuingAware;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.util.Iterator;
import java.util.Map.Entry;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;


/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

/**
 * This class defines a Cancellable standalone Runnable context, which wraps the operations of performing the HTTP Request and waiting for the response.
 * @author  William Choi <avextk@gmail.com>
 * @see     TimeLimited
 * @see     QueuingAware
 * @see     NetChannelRunnableRequest
 */
public class HttpRunnableRequest extends TimeLimited implements QueuingAware, Runnable, ChannelRequestIntf {
    final static int MAXIMA_TIMEOUT = 8000; //millis

    private int         ret_code;
    private String      ret_mesg;
    private boolean     finished;
    private boolean     canceled;
    private Object      rqstid;
    private DLogger     logger;
    private Queue<Queue>                allqueues;
    private HttpURLConnection           l_httpcon;
    private ChannelResponseIntf   l_handler;


    /**
     * Generic constructor which can explicity specify the timeout value of the request
     * @param httpconn  Reference of the user HttpURLConnection instance
     * @param handler   Reference of the user handler which implements the NetChannelResponseHandler interface
     * @param requestID The user defined identification for the request
     * @param timeout   The timeout value explicitly specified by the user for the request
     */
    public HttpRunnableRequest(HttpURLConnection httpconn, ChannelResponseIntf handler, Object callbackID, int timeout){
        super(0); //Do not set aging timeout while in waiting queue

        if ((null == callbackID) || (null == httpconn))
            throw new IllegalArgumentException("Null argument to the constructor.");

        if (MAXIMA_TIMEOUT < timeout)
            throw new IllegalArgumentException("Timeout value exceeds the MAXIMA_TIMEOUT.");

        ret_code    = 0;
        ret_mesg    = null;
        finished   = false;
        canceled   = false;
        l_httpcon   = httpconn;
        l_handler   = handler;
        rqstid      = callbackID;
        logger      = DLogger.getInstance(this);
        allqueues   = new ConcurrentLinkedQueue<Queue>();
        
        l_httpcon.setConnectTimeout(timeout);
        l_httpcon.setReadTimeout(timeout);
        
    }

    /**
     * The implemented method of the Runnable interface, it defines the context of the Thread
     */
    @Override
    public void run() {
        logger.trace("entering...");
        
        do{
            if (this.isExpired()){
                logger.debug("The request has been aged out.");
                break;
            }
            try {
                logger.info("Waiting for server response...");
                ret_code = l_httpcon.getResponseCode();
                ret_mesg = l_httpcon.getResponseMessage();
                finished = true;
                leaveAllQueues();
                logger.info("response code = " + ret_code + ", message = " + ret_mesg);
            } catch (IOException ex) {
                finished = true;
                logger.error(ex);
                if (null != l_handler){
                    logger.debug("Passing the exception to handler's callback...");
                    l_handler.handle(rqstid, ex);
                    logger.debug("returned from handler's callback for exception notification.");
                }
                break; //Exit the context
            }

            //we reach here means we didn't get any Exception before
            if (null != l_handler){
                try {
                    logger.debug("calling handler's callback...");
                    l_handler.handle(rqstid, ret_code, ret_mesg, l_httpcon.getInputStream());
                    logger.debug("returned from handler's callback.");
                } catch (IOException ex) {
                    logger.error(ex);
                }
            }

        }while(false);

        // Notify all threads who are now waiting this request to join back
        synchronized(this){
            logger.debug("Notifying that I'm joing back.");
            this.notifyAll();
        }

        logger.trace("exit.");
    }

    @Override
    /**
     * The implemented method of the Cancellabe interface. Instance owner can call this method to cancel the request instance.
     */
    public boolean cancel() throws IllegalStateException {
        if (finished)
            throw new IllegalStateException("This request has already completed.");

        if (canceled)
            throw new IllegalStateException("This request has already been cancelled.");

        logger.debug("Cancelling...");
        leaveAllQueues();
        logger.debug("Disconnect the Http Connection.");
        l_httpcon.disconnect();
        canceled = true;
        logger.debug("Cancelled.");
        return isCancelled();
    }

    @Override
    public int getResponseCode(){
        return ret_code;
    }

    @Override
    public String getResponseMessage(){
        return ret_mesg;
    }

    @Override
    public InputStream getInputStream() throws IOException{
        return l_httpcon.getInputStream();
    }

    @Override
    public boolean isCompleted(){
        return finished;
    }

    @Override
    public boolean isCancelled() {
        return canceled;
    }

    @Override
    public void enterQueue(Queue q) {
        allqueues.add(q);
    }

    @Override
    public boolean exitQueue(Queue q) {
        allqueues.remove(q);
        return true;
    }

    @Override
    public boolean leaveQueue(Queue q) {
        boolean ret = false;
        ret = q.remove(this);
        exitQueue(q);

        return ret;
    }

    @Override
    public boolean isQueued() {
        return (!allqueues.isEmpty());
    }

    @Override
    public long leaveAllQueues() {
        long ret = 0;

        if (isQueued()){
            logger.debug("Remove the request itself from all related queues (" + allqueues.size() + ").");
            Queue q;
            while (null != (q = allqueues.peek())){
                logger.debug("Leaving queue #" + ret);
                leaveQueue(q);
                ret++;
            }
        }
        return ret;
    }

    @Override
    public void join() throws InterruptedException {
        synchronized(this){
            this.wait(MAXIMA_TIMEOUT + 1000);
        }
    }

    @Override
    public int getParamCount() {
        throw new UnsupportedOperationException("Not supported yet.");
    }

    @Override
    public Iterator<Entry<?, ?>> iterator() {
        throw new UnsupportedOperationException("Not supported yet.");
    }


}
